package pinboard.dataLayer.binPack;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import pinboard.dataLayer.ByteArrayConvert;
import pinboard.dataLayer.RefBag;

class BinDeserializeMgr extends ObjectPackager {

    BinDeserializeMgr(PackAttributeCache cache) {
        markCache = cache;
    }

    public <T> T toObject(byte[] input, RefBag<Integer> offset, Class<T> tClass) throws IOException {
        if (isSimple(tClass)) {
            return (T) bytesToSimple(input, offset, tClass);
        } else {
            return (T) bytesToObject(input, offset, tClass);
        }
    }

    private Object bytesToSimple(byte[] input, RefBag<Integer> offset, Class<?> tClass) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Object result = null;
        if (tClass == int.class) {
            result = ByteArrayConvert.bytes2Int(input, offset.data);
            offset.data = offset.data + 4;
        } else if (tClass == byte.class) {
            result = input[offset.data];
            offset.data = offset.data + 1;
        } else if (tClass == float.class) {
            result = ByteArrayConvert.bytes2Float(input, offset.data);
            offset.data = offset.data + 4;
        } else if (tClass == long.class) {
            result = ByteArrayConvert.bytes2Long(input, offset.data);
            offset.data = offset.data + 8;
        } else if (tClass == double.class) {
            result = ByteArrayConvert.bytes2Double(input, offset.data);
            offset.data = offset.data + 8;
        } else if (tClass == String.class) {
            result = bytes2String(input, offset);
        }
        return result;
    }

    private String bytes2String(byte[] input, RefBag<Integer> offset) throws UnsupportedEncodingException {
        int len = ByteArrayConvert.bytes2Int(input, offset.data);
        offset.data = offset.data + 4;
        byte[] strData = new byte[len];
        System.arraycopy(input, offset.data, strData, 0, len);
        String result = new String(strData, defaultCharsetName);
        offset.data = offset.data + len;
        return result;
    }

    private <T> T bytesToObject(byte[] input, RefBag<Integer> offset, Class<T> tClass) {
        T result = null;
        if (input == null) {
            return null;
        } else {
            List<PackAttributeFormat> list = markCache.get(tClass);
            try {
                result = tClass.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            for (PackAttributeFormat pbf : list) {
                try {
                    Field fd = tClass.getField(pbf.fieldName);
                    if (pbf.serializerName.length() > 0) {
                        IBinSerializer serializer = BinSerializerCache.get(pbf.serializerName);
                        Object value = addCustom(input, offset, serializer, pbf.fieldType, pbf.fieldGenericType);
                        fd.set(result, value);
                    } else if (pbf.isGroup) {
                        Object value = addGroup(input, offset, pbf.fieldType, pbf.fieldGenericType);
                        fd.set(result, value);
                    } else if (isSimple(pbf.fieldType)) {
                        Object value = bytesToSimple(input, offset, pbf.fieldType);
                        fd.set(result, value);
                    } else {
                        //其他类型 继续递归
                        Object value = bytesToObjectImp(pbf.fixedRange, input, offset, pbf.fieldType);
                        fd.set(result, value);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
        return result;
    }

    private Object bytesToObjectImp(int fixedRange, byte[] input, RefBag<Integer> offset, Class<?> tClass) {
        Object value = null;
        if (fixedRange < 0) {
            int len = ByteArrayConvert.bytes2Int(input, offset.data);
            offset.data = offset.data + 4;
            if (len != 0) {
                value = bytesToObject(input, offset, tClass);
            }
        } else if (fixedRange == 0) {
            value = bytesToObject(input, offset, tClass);
        } else {
            value = bytesToObject(input, offset, tClass);
        }
        return value;
    }

    private Object addCustom(byte[] input, RefBag<Integer> offset, IBinSerializer serializer, Class<?> fieldType, Type fieldGType) {
        if (fieldType.isArray()) {
            return serializer.toArray(input, offset);
        } else if (List.class.isAssignableFrom(fieldType)) {
            return Arrays.asList(serializer.toArray(input, offset));//基本测试了类型转换问题，结论正常。
        } else {
            return serializer.toObject(input, offset);
        }
    }

    private byte[] addDataToBytes(byte[] input, RefBag<Integer> offset) {
        int len = ByteArrayConvert.bytes2Int(input, offset.data);
        offset.data = offset.data + 4;
        byte[] result = new byte[len];
        System.arraycopy(input, offset.data, result, 0, len);
        offset.data = offset.data + len;
        return result;
    }

    private String[] addDataToStrings(byte[] input, RefBag<Integer> offset) throws UnsupportedEncodingException {
        int len = ByteArrayConvert.bytes2Int(input, offset.data);
        offset.data = offset.data + 4;
        String[] result = new String[len];
        for (int i = 0; i < result.length; i++) {
            result[i] = bytes2String(input, offset);
        }
        return result;
    }

    private int[] addDataToInts(byte[] input, RefBag<Integer> offset) {
        int len = ByteArrayConvert.bytes2Int(input, offset.data);
        offset.data = offset.data + 4;
        int[] result = new int[len];
        for (int i = 0; i < result.length; i++) {
            result[i] = ByteArrayConvert.bytes2Int(input, offset.data);
            offset.data = offset.data + 4;
        }
        return result;
    }

    private float[] addDataToFloats(byte[] input, RefBag<Integer> offset) {
        int len = ByteArrayConvert.bytes2Int(input, offset.data);
        offset.data = offset.data + 4;
        float[] result = new float[len];
        for (int i = 0; i < result.length; i++) {
            result[i] = ByteArrayConvert.bytes2Float(input, offset.data);
            offset.data = offset.data + 4;
        }
        return result;
    }

    private long[] addDataToLongs(byte[] input, RefBag<Integer> offset) {
        int len = ByteArrayConvert.bytes2Int(input, offset.data);
        offset.data = offset.data + 4;
        long[] result = new long[len];
        for (int i = 0; i < result.length; i++) {
            result[i] = ByteArrayConvert.bytes2Long(input, offset.data);
            offset.data = offset.data + 8;
        }
        return result;
    }

    private double[] addDataToDoubles(byte[] input, RefBag<Integer> offset) {
        int len = ByteArrayConvert.bytes2Int(input, offset.data);
        offset.data = offset.data + 4;
        double[] result = new double[len];
        for (int i = 0; i < result.length; i++) {
            result[i] = ByteArrayConvert.bytes2Double(input, offset.data);
            offset.data = offset.data + 4;
        }
        return result;
    }

    private <T> T[] addArray(byte[] input, RefBag<Integer> offset, Class<T> tClass) {
        List<T> list = addList(input, offset, tClass);
        T[] result = (T[]) Array.newInstance(tClass, list.size());
        list.toArray(result);
        return result;
    }

    private <T> List<T> addList(byte[] input, RefBag<Integer> offset, Class<T> tClass) {
        int len = ByteArrayConvert.bytes2Int(input, offset.data);
        offset.data = offset.data + 4;
        ArrayList<T> result = new ArrayList<>(len);
        for (int i = 0; i < len; i++) {
            int contentLen = ByteArrayConvert.bytes2Int(input, offset.data);
            offset.data = offset.data + 4;
            if (contentLen == 0) {
                result.add(null);
            } else {
                result.add(bytesToObject(input, offset, tClass));
            }
        }
        return result;
    }

    private Object addGroup(byte[] input, RefBag<Integer> offset, Class<?> tClass, Type gType) throws UnsupportedEncodingException {
        if (tClass.isArray()) {
            if (tClass == byte[].class) {
                return addDataToBytes(input, offset);
            } else if (tClass == String[].class) {
                return addDataToStrings(input, offset);
            } else if (tClass == int[].class) {
                return addDataToInts(input, offset);
            } else if (tClass == float[].class) {
                return addDataToFloats(input, offset);
            } else if (tClass == long[].class) {
                return addDataToLongs(input, offset);
            } else if (tClass == double[].class) {
                return addDataToDoubles(input, offset);
            } else {
                return addArray(input, offset, tClass.getComponentType());
            }
        } else if (List.class.isAssignableFrom(tClass)) {
            Class<?> elementType = getFirstArgClass(gType);
            if (elementType == String.class) {
                String[] strArray = addDataToStrings(input, offset);
                return new ArrayList<>(Arrays.asList(strArray));
            } else {
                return addList(input, offset, elementType);
            }
        }
        return null;
    }
}
